package com.xiyuan.smartutils;

import com.xiyuan.smartutils.constants.CodeConstants;

import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.*;

/**
 * Zip/GZip 压缩与解压
 *
 * @version v1.0.0 @author xiyuan-lgz 2017-8-27 新建与整理
 */

public final class Zips implements CodeConstants {
    
    private Zips() {}
    /*****************************************************************************************/
    // Zip压缩和解压
    /*****************************************************************************************/
    
    /**
     * 压缩目录成zip文件
     *
     * @param fileDirPath   文件目录路径
     * @param zipExportPath zip文件路径
     * @throws Exception 异常
     */
    public static void zip(String fileDirPath, String zipExportPath) throws Exception {
        Asserts.nonEmpty(fileDirPath, "文件目录路径");
        Asserts.nonEmpty(zipExportPath, "ZIP导出路径");
        
        File fileDir = new File(fileDirPath);
        Asserts.as(fileDir.exists() ? null : "文件目录路径不存在");
        
        // 转化为/方式的绝对路径
        String fileDirCanonicalPath = Files.toLinuxPath(fileDir.getCanonicalPath());
        
        FileOutputStream fos = new FileOutputStream(zipExportPath);
        ZipOutputStream zos = new ZipOutputStream(fos, _UTF_8_C_);
        zos.setLevel(Deflater.BEST_COMPRESSION);
        
        Files.filter(fileDir, file -> {
            if (!(file.isFile() && file.canRead())) {
                return false;
            }
            
            try {
                String name = Files.toLinuxPath(file.getCanonicalPath());
                if (file.isDirectory()) {
                    name = name.substring(fileDirCanonicalPath.length() + 1) + "/";
                }
                else {
                    name = name.substring(fileDirCanonicalPath.length() + 1);
                }
                
                ZipEntry ze = new ZipEntry(name);
                ze.setSize(file.length());
                ze.setTime(file.lastModified());
                zos.putNextEntry(ze);
                
                if (ze.isDirectory()) {
                    // 目录
                    zos.closeEntry();
                }
                else {// 文件
                    try (InputStream is = java.nio.file.Files.newInputStream(file.toPath())) {
                        IoUtils.putBytes(is, zos);
                    }
                    zos.closeEntry();
                }
            }
            catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
            
            return true;
        }, null);
        
        zos.close();
    }
    
    /**
     * 解压ZIP文件
     *
     * @param zip   ZIP文件
     * @param toDir 解压目录
     * @return 返回当前列表
     * @throws IOException 异常
     */
    public static List<File> unzip(File zip, File toDir) throws IOException {
        List<File> fileList = new ArrayList<File>();
        ZipFile zipFile = new ZipFile(zip);
        Enumeration<?> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) entries.nextElement();
            if (entry.isDirectory()) {
                Files.mkdir(new File(toDir, entry.getName()));
                continue;
            }
            
            InputStream input = null;
            OutputStream output = null;
            try {
                File file = new File(toDir, entry.getName());
                
                input = zipFile.getInputStream(entry);
                output = java.nio.file.Files.newOutputStream(file.toPath());
                IoUtils.putBytes(input, output);
                
                fileList.add(file);
            }
            finally {
                try {
                    if (input != null) {
                        input.close();
                    }
                }
                catch (Exception e) {
                }
                
                try {
                    if (output != null) {
                        output.close();
                    }
                }
                catch (Exception e) {
                }
            }
        }
        
        zipFile.close();
        return fileList;
    }
    
    /*****************************************************************************************/
    // GZip
    /*****************************************************************************************/
    
    /**
     * GZIP压缩，转化为正常输出流
     *
     * @param output 输出流
     * @return ByteArrayOutputStream
     * @throws IOException 异常
     */
    public static ByteArrayOutputStream gzip(ByteArrayOutputStream output) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (GZIPOutputStream gzip = new GZIPOutputStream(bos)) {
            output.writeTo(gzip);
            gzip.finish();
            
            return bos;
        }
    }
    
    /**
     * GZIP压缩字符串，要求传入编码
     *
     * @param src      原字符串
     * @param encoding 编码格式
     * @return 得到字节数组
     */
    public static byte[] gzipString(String src, String encoding) throws IOException {
        if (src == null) {
            return new byte[0];
        }
        
        try {
            return gzip(src.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e) {
            return new byte[0];
        }
    }
    
    /**
     * GZIP字节压缩
     *
     * @param src 原字节数组
     * @return 压缩全的字节数组
     */
    public static byte[] gzip(byte[] src) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        
        try (GZIPOutputStream gzip = new GZIPOutputStream(output)) {
            gzip.write(src);
            gzip.finish();
            
            return output.toByteArray();
        }
    }
    
    /**
     * GZIP 解压字节数组
     *
     * @param src 原字节数组
     * @return 得到解压后的字节数组
     * @throws IOException IO异常需要捕捉
     */
    public static byte[] unGZip(byte[] src) throws IOException {
        ByteArrayInputStream input = new ByteArrayInputStream(src);
        
        try (GZIPInputStream gzip = new GZIPInputStream(input)) {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            IoUtils.putBytes(gzip, output);
            
            return output.toByteArray();
        }
    }
    
    /**
     * GZIP解压，转化为正常输入流
     *
     * @param input 输入流
     * @return 正常的ByteArrayInputStream
     * @throws IOException IO异常需要捕捉
     */
    public static ByteArrayInputStream unGZip(InputStream input) throws IOException {
        try (GZIPInputStream gzip = new GZIPInputStream(input)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            IoUtils.putBytes(gzip, out);
            
            return new ByteArrayInputStream(out.toByteArray());
        }
    }
    
    /**
     * GZIP解压，得到字符串
     *
     * @param src      原字节数组
     * @param encoding 编码
     * @return 得到解压后的字节数组，再编码成字符串
     */
    public static String unGZipString(byte[] src, String encoding) {
        try {
            byte[] dest = unGZip(src);
            return new String(dest, encoding);
        }
        catch (UnsupportedEncodingException e) {
            throw Asserts.exception("非法的编码参数[%s]", encoding);
        }
        catch (IOException e) {
            throw Asserts.exception(e);
        }
    }
    
    /**
     * GZIP解压文件到文件夹
     *
     * @param gzip  解压文件
     * @param toDir 解压目录
     * @return 解压后的文件
     * @throws IOException 异常信息
     */
    public static File unGZip(File gzip, File toDir) throws IOException {
        Files.mkdir(toDir);
        File file = new File(toDir, gzip.getName());
        
        try (FileInputStream fis = new FileInputStream(gzip);
             FileOutputStream fos = new FileOutputStream(file);
             GZIPInputStream input = new GZIPInputStream(fis)) {
            
            IoUtils.putBytes(input, fos);
        }
        
        return file;
    }
}
